DonationModal.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. 'use client';
  2. import { useState, useEffect } from 'react';
  3. import { fetchApi } from '@/lib/utils/client';
  4. import './donation-modal.scss';
  5. type CrewMemberInfo = {
  6. crewMemberID: number;
  7. nickname: string;
  8. thumb: string|null;
  9. channelName: string|null;
  10. };
  11. type ActiveCrew = {
  12. crewSessionID: number;
  13. title: string;
  14. crewName: string;
  15. members: CrewMemberInfo[];
  16. }|null;
  17. type Props = {
  18. channelSID: string;
  19. onClose: () => void;
  20. };
  21. export default function DonationModal({ channelSID, onClose }: Props)
  22. {
  23. const [amount, setAmount] = useState(1000);
  24. const [message, setMessage] = useState('');
  25. const [sendName, setSendName] = useState('');
  26. const [activeCrew, setActiveCrew] = useState<ActiveCrew>(null);
  27. const [selectedMember, setSelectedMember] = useState<number|null>(null);
  28. const [sending, setSending] = useState(false);
  29. const [done, setDone] = useState(false);
  30. const presetAmounts = [1000, 3000, 5000, 10000, 30000, 50000];
  31. useEffect(() => {
  32. // 활성 크루 세션 조회
  33. fetchApi<ActiveCrew>(`/api/donation/crew/active/${channelSID}`)
  34. .then(res => {
  35. if (res.data) setActiveCrew(res.data);
  36. })
  37. .catch(() => {});
  38. }, [channelSID]);
  39. const handleSend = async () => {
  40. if (amount < 1000) {
  41. alert('최소 후원 금액은 1,000원입니다.');
  42. return;
  43. }
  44. if (!sendName.trim()) {
  45. alert('보내는 사람 이름을 입력해 주세요.');
  46. return;
  47. }
  48. setSending(true);
  49. try {
  50. const body: Record<string, unknown> = {
  51. channelSID,
  52. amount,
  53. message: message || null,
  54. sendName: sendName.trim()
  55. };
  56. if (activeCrew && selectedMember) {
  57. body.crewSessionID = activeCrew.crewSessionID;
  58. body.crewMemberID = selectedMember;
  59. }
  60. await fetchApi('/api/donation/send', {
  61. method: 'POST',
  62. body
  63. });
  64. setDone(true);
  65. } catch (err: unknown) {
  66. alert(err instanceof Error ? err.message : '후원에 실패했습니다.');
  67. } finally {
  68. setSending(false);
  69. }
  70. };
  71. if (done) {
  72. return (
  73. <div className="donation-modal">
  74. <div className="donation-modal__overlay" onClick={onClose} />
  75. <div className="donation-modal__box">
  76. <div className="donation-modal__done">
  77. <div className="donation-modal__done-icon">🎉</div>
  78. <p className="donation-modal__done-text">{amount.toLocaleString()}원 후원 완료!</p>
  79. <button type="button" className="donation-modal__btn donation-modal__btn--primary" onClick={onClose}>닫기</button>
  80. </div>
  81. </div>
  82. </div>
  83. );
  84. }
  85. return (
  86. <div className="donation-modal">
  87. <div className="donation-modal__overlay" onClick={onClose} />
  88. <div className="donation-modal__box">
  89. <div className="donation-modal__header">
  90. <h2 className="donation-modal__title">후원하기</h2>
  91. <button type="button" className="donation-modal__close" onClick={onClose}>&times;</button>
  92. </div>
  93. {/* 보내는 사람 */}
  94. <div className="donation-modal__field">
  95. <label>닉네임</label>
  96. <input type="text" value={sendName} onChange={e => setSendName(e.target.value)} placeholder="보내는 사람" maxLength={20} />
  97. </div>
  98. {/* 금액 */}
  99. <div className="donation-modal__field">
  100. <label>금액</label>
  101. <div className="donation-modal__presets">
  102. {presetAmounts.map(a => (
  103. <button
  104. type="button"
  105. key={a}
  106. className={`donation-modal__preset${amount === a ? ' donation-modal__preset--active' : ''}`}
  107. onClick={() => setAmount(a)}
  108. >
  109. {a.toLocaleString()}원
  110. </button>
  111. ))}
  112. </div>
  113. <input
  114. type="number"
  115. min={1000}
  116. max={10000000}
  117. step={1000}
  118. value={amount}
  119. onChange={e => setAmount(Number(e.target.value))}
  120. />
  121. </div>
  122. {/* 메시지 */}
  123. <div className="donation-modal__field">
  124. <label>메시지 (선택)</label>
  125. <textarea value={message} onChange={e => setMessage(e.target.value)} placeholder="응원 메시지를 남겨주세요" maxLength={100} rows={2} />
  126. </div>
  127. {/* 크루원 선택 */}
  128. {activeCrew && activeCrew.members.length > 0 && (
  129. <div className="donation-modal__crew">
  130. <label className="donation-modal__crew-label">
  131. 크루원에게 후원 <span className="donation-modal__crew-tag">{activeCrew.crewName}</span>
  132. </label>
  133. <div className="donation-modal__crew-list">
  134. <button
  135. type="button"
  136. className={`donation-modal__crew-item${selectedMember === null ? ' donation-modal__crew-item--active' : ''}`}
  137. onClick={() => setSelectedMember(null)}
  138. >
  139. <div className="donation-modal__crew-thumb donation-modal__crew-thumb--default">채널</div>
  140. <span>채널 주인</span>
  141. </button>
  142. {activeCrew.members.map(m => (
  143. <button
  144. type="button"
  145. key={m.crewMemberID}
  146. className={`donation-modal__crew-item${selectedMember === m.crewMemberID ? ' donation-modal__crew-item--active' : ''}`}
  147. onClick={() => setSelectedMember(m.crewMemberID)}
  148. >
  149. {m.thumb ? (
  150. <img src={m.thumb} alt="" className="donation-modal__crew-thumb" />
  151. ) : (
  152. <div className="donation-modal__crew-thumb donation-modal__crew-thumb--default">{m.nickname.charAt(0)}</div>
  153. )}
  154. <span>{m.nickname}</span>
  155. </button>
  156. ))}
  157. </div>
  158. </div>
  159. )}
  160. {/* 전송 */}
  161. <div className="donation-modal__footer">
  162. <button type="button" className="donation-modal__btn" onClick={onClose}>취소</button>
  163. <button type="button" className="donation-modal__btn donation-modal__btn--primary" onClick={handleSend} disabled={sending}>
  164. {sending ? '전송 중...' : `${amount.toLocaleString()}원 후원`}
  165. </button>
  166. </div>
  167. </div>
  168. </div>
  169. );
  170. }